<!DOCTYPE html>
<html>

<head>
    <!-- Code taken from http://stevehanov.ca/blog/index.php?id=143 -->
    <script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
        crossorigin="anonymous"></script>
    <style>
        #dropTarget {
            width: 100%;
            min-width: 500px;
            height: 1000px;
            background: lavender;
            border: 1px solid lightgray;
        }

        #details-container {
            background: #333;
            color: #ededed;
            font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
            font-size: 14px;
        }

        #details-container span {
            margin: 5px;
            display: block;
            padding: 5px;
            width: 160px;
        }
    </style>
</head>

<body id="dropTarget">

    <div id="details-container"></div>
    <div id="font-container"></div>
    <script>
        var dropTarget = document.getElementById("dropTarget");
        dropTarget.ondragover = function (e) {
            e.preventDefault();
        };
        dropTarget.ondrop = function (e) {
            e.preventDefault();

            if (!e.dataTransfer || !e.dataTransfer.files) {
                alert("Your browser didn't include any files in the drop event");
                return;
            }

            var reader = new FileReader();
            reader.readAsArrayBuffer(e.dataTransfer.files[0]);
            reader.onload = function (e) {
                ShowTtfFile(reader.result);
            };

        };

        function assert(condition) {
            if (!condition) alert("False assert");
        }

        function BinaryReader(arrayBuffer) {
            assert(arrayBuffer instanceof ArrayBuffer);
            this.pos = 0;
            this.data = new Uint8Array(arrayBuffer);
        }

        BinaryReader.prototype = {
            seek: function (pos) {
                assert(pos >= 0 && pos <= this.data.length);
                var oldPos = this.pos;
                this.pos = pos;
                return oldPos;
            },

            tell: function () {
                return this.pos;
            },

            getUint8: function () {
                assert(this.pos < this.data.length);
                return this.data[this.pos++];
            },

            getUint16: function () {
                return ((this.getUint8() << 8) | this.getUint8()) >>> 0;
            },

            getUint32: function () {
                return this.getInt32() >>> 0;
            },

            getInt16: function () {
                var result = this.getUint16();
                if (result & 0x8000) {
                    result -= (1 << 16);
                }
                return result;
            },

            getInt32: function () {
                return ((this.getUint8() << 24) |
                    (this.getUint8() << 16) |
                    (this.getUint8() << 8) |
                    (this.getUint8()));
            },

            getFword: function () {
                return this.getInt16();
            },

            get2Dot14: function () {
                return this.getInt16() / (1 << 14);
            },

            getFixed: function () {
                return this.getInt32() / (1 << 16);
            },

            getString: function (length) {
                var result = "";
                for (var i = 0; i < length; i++) {
                    result += String.fromCharCode(this.getUint8());
                }
                return result;
            },

            getDate: function () {
                var macTime = this.getUint32() * 0x100000000 + this.getUint32();
                var utcTime = macTime * 1000 + Date.UTC(1904, 1, 1);
                return new Date(utcTime);
            }
        };

        function TrueTypeFont(arrayBuffer) {
            this.file = new BinaryReader(arrayBuffer);
            this.tables = this.readOffsetTables(this.file);
            this.readHeadTable(this.file);
            this.length = this.glyphCount();
        }

        TrueTypeFont.prototype = {
            readOffsetTables: function (file) {
                var tables = {};
                this.scalarType = file.getUint32();
                var numTables = file.getUint16();
                this.searchRange = file.getUint16();
                this.entrySelector = file.getUint16();
                this.rangeShift = file.getUint16();

                for (var i = 0; i < numTables; i++) {
                    var tag = file.getString(4);
                    tables[tag] = {
                        checksum: file.getUint32(),
                        offset: file.getUint32(),
                        length: file.getUint32()
                    };

                    if (tag !== 'head') {
                        assert(this.calculateTableChecksum(file, tables[tag].offset,
                            tables[tag].length) === tables[tag].checksum);
                    }
                }

                return tables;
            },

            calculateTableChecksum: function (file, offset, length) {
                var old = file.seek(offset);
                var sum = 0;
                var nlongs = ((length + 3) / 4) | 0;
                while (nlongs--) {
                    sum = (sum + file.getUint32() & 0xffffffff) >>> 0;
                }

                file.seek(old);
                return sum;
            },



            glyphCount: function () {
                assert("maxp" in this.tables);
                var old = this.file.seek(this.tables["maxp"].offset + 4);
                var count = this.file.getUint16();
                this.file.seek(old);
                return count;
            },

            readHeadTable: function (file) {
                assert("head" in this.tables);
                file.seek(this.tables["head"].offset);

                this.version = file.getFixed();
                this.fontRevision = file.getFixed();
                this.checksumAdjustment = file.getUint32();
                this.magicNumber = file.getUint32();
                assert(this.magicNumber === 0x5f0f3cf5);
                this.flags = file.getUint16();
                this.unitsPerEm = file.getUint16();
                this.created = file.getDate();
                this.modified = file.getDate();
                this.xMin = file.getFword();
                this.yMin = file.getFword();
                this.xMax = file.getFword();
                this.yMax = file.getFword();
                this.macStyle = file.getUint16();
                this.lowestRecPPEM = file.getUint16();
                this.fontDirectionHint = file.getInt16();
                this.indexToLocFormat = file.getInt16();
                this.glyphDataFormat = file.getInt16();
            },

            getGlyphOffset: function (index) {
                assert("loca" in this.tables);
                var table = this.tables["loca"];
                var file = this.file;
                var offset, old;

                if (this.indexToLocFormat === 1) {
                    old = file.seek(table.offset + index * 4);
                    offset = file.getUint32();
                } else {
                    old = file.seek(table.offset + index * 2);
                    offset = file.getUint16() * 2;
                }

                file.seek(old);

                return offset + this.tables["glyf"].offset;
            },

            readGlyph: function (index) {
                var offset = this.getGlyphOffset(index);
                var file = this.file;

                if (offset >= this.tables["glyf"].offset + this.tables["glyf"].length) {
                    return null;
                }

                assert(offset >= this.tables["glyf"].offset);
                assert(offset < this.tables["glyf"].offset + this.tables["glyf"].length);

                file.seek(offset);

                var glyph = {
                    numberOfContours: file.getInt16(),
                    xMin: file.getFword(),
                    yMin: file.getFword(),
                    xMax: file.getFword(),
                    yMax: file.getFword()
                };

                assert(glyph.numberOfContours >= -1);

                if (glyph.numberOfContours === -1) {
                    this.readCompoundGlyph(file, glyph);
                } else {
                    this.readSimpleGlyph(file, glyph);
                }

                return glyph;
            },

            readSimpleGlyph: function (file, glyph) {

                var ON_CURVE = 1,
                    X_IS_BYTE = 2,
                    Y_IS_BYTE = 4,
                    REPEAT = 8,
                    X_DELTA = 16,
                    Y_DELTA = 32;

                glyph.type = "simple";
                glyph.contourEnds = [];
                var points = glyph.points = [];

                for (var i = 0; i < glyph.numberOfContours; i++) {
                    glyph.contourEnds.push(file.getUint16());
                }

                // skip over intructions
                file.seek(file.getUint16() + file.tell());

                if (glyph.numberOfContours === 0) {
                    return;
                }

                var numPoints = Math.max.apply(null, glyph.contourEnds) + 1;

                var flags = [];

                for (i = 0; i < numPoints; i++) {
                    var flag = file.getUint8();
                    flags.push(flag);
                    points.push({
                        onCurve: (flag & ON_CURVE) > 0
                    });

                    if (flag & REPEAT) {
                        var repeatCount = file.getUint8();
                        assert(repeatCount > 0);
                        i += repeatCount;
                        while (repeatCount--) {
                            flags.push(flag);
                            points.push({
                                onCurve: (flag & ON_CURVE) > 0
                            });
                        }
                    }
                }

                function readCoords(name, byteFlag, deltaFlag, min, max) {
                    var value = 0;

                    for (var i = 0; i < numPoints; i++) {
                        var flag = flags[i];
                        if (flag & byteFlag) {
                            if (flag & deltaFlag) {
                                value += file.getUint8();
                            } else {
                                value -= file.getUint8();
                            }
                        } else if (~flag & deltaFlag) {
                            value += file.getInt16();
                        } else {
                            // value is unchanged.
                        }

                        points[i][name] = value;
                    }
                }

                readCoords("x", X_IS_BYTE, X_DELTA, glyph.xMin, glyph.xMax);
                readCoords("y", Y_IS_BYTE, Y_DELTA, glyph.yMin, glyph.yMax);
            },

            readCompoundGlyph: function (file, glyph) {
                var ARG_1_AND_2_ARE_WORDS = 1,
                    ARGS_ARE_XY_VALUES = 2,
                    ROUND_XY_TO_GRID = 4,
                    WE_HAVE_A_SCALE = 8,
                    // RESERVED              = 16
                    MORE_COMPONENTS = 32,
                    WE_HAVE_AN_X_AND_Y_SCALE = 64,
                    WE_HAVE_A_TWO_BY_TWO = 128,
                    WE_HAVE_INSTRUCTIONS = 256,
                    USE_MY_METRICS = 512,
                    OVERLAP_COMPONENT = 1024;

                glyph.type = "compound";
                glyph.components = [];

                var flags = MORE_COMPONENTS;
                while (flags & MORE_COMPONENTS) {
                    var arg1, arg2;

                    flags = file.getUint16();
                    var component = {
                        glyphIndex: file.getUint16(),
                        matrix: {
                            a: 1, b: 0, c: 0, d: 1, e: 0, f: 0
                        }
                    };

                    if (flags & ARG_1_AND_2_ARE_WORDS) {
                        arg1 = file.getInt16();
                        arg2 = file.getInt16();
                    } else {
                        arg1 = file.getUint8();
                        arg2 = file.getUint8();
                    }

                    if (flags & ARGS_ARE_XY_VALUES) {
                        component.matrix.e = arg1;
                        component.matrix.f = arg2;
                    } else {
                        component.destPointIndex = arg1;
                        component.srcPointIndex = arg2;
                    }

                    if (flags & WE_HAVE_A_SCALE) {
                        component.matrix.a = file.get2Dot14();
                        component.matrix.d = component.matrix.a;
                    } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
                        component.matrix.a = file.get2Dot14();
                        component.matrix.d = file.get2Dot14();
                    } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
                        component.matrix.a = file.get2Dot14();
                        component.matrix.b = file.get2Dot14();
                        component.matrix.c = file.get2Dot14();
                        component.matrix.d = file.get2Dot14();
                    }

                    glyph.components.push(component);
                }

                if (flags & WE_HAVE_INSTRUCTIONS) {
                    file.seek(file.getUint16() + file.tell());
                }
            },

            drawGlyph: function (index, ctx) {

                var glyph = this.readGlyph(index);

                if (glyph === null || glyph.type !== "simple") {
                    return false;
                }

                var p = 0,
                    c = 0,
                    first = 1;

                while (p < glyph.points.length) {
                    var point = glyph.points[p];
                    if (first === 1) {
                        ctx.moveTo(point.x, point.y);
                        first = 0;
                    } else {
                        ctx.lineTo(point.x, point.y);
                    }

                    if (p === glyph.contourEnds[c]) {
                        c += 1;
                        first = 1;
                    }

                    p += 1;
                }

                return true;
            }
        }

        function CreateInformation(s, v, container) {
            var span = document.createElement("span");
            span.innerHTML = s + ": " + v;
            container.appendChild(span);
        }

        function ShowTtfFile(arrayBuffer) {
            var font = new TrueTypeFont(arrayBuffer);

            var details = document.getElementById("details-container");

            while (details.firstChild) details.removeChild(details.firstChild);

            var content = "<table class='table table-striped'><thead><tr><td>Tag</td><td>Checksum</td><td>Offset</td><td>Length</td></tr></thead><tbody>";
            for (var property in font.tables) {
                if (font.tables.hasOwnProperty(property)) {
                    var tab = font.tables[property];
                    content += "<tr>";
                    content += "<td>" + property + "</td>";
                    content += "<td>" + tab.checksum + "</td>";
                    content += "<td>" + tab.offset + "</td>";
                    content += "<td>" + tab.length + "</td>";
                    content += "</tr>";
                }
            }

            content += "</tbody></table>";
            $("#details-container").append(content);

            CreateInformation("Version", font.version, details);
            CreateInformation("Revision", font.fontRevision, details);
            CreateInformation("Checksum adjustment", font.checksumAdjustment, details);
            CreateInformation("Magic Number", font.magicNumber, details);
            CreateInformation("Flags", font.flags, details);
            CreateInformation("Units Per Em", font.unitsPerEm, details);
            CreateInformation("Created", font.created, details);
            CreateInformation("Modified", font.modified, details);
            CreateInformation("Min X", font.xMin, details);
            CreateInformation("Min Y", font.yMin, details);
            CreateInformation("Max X", font.xMax, details);
            CreateInformation("Max Y", font.yMax, details);
            CreateInformation("Mac Style", font.macStyle, details);
            CreateInformation("Lowest recommended PPEM", font.lowestRecPPEM, details);
            CreateInformation("Font Direction", font.fontDirectionHint, details);
            CreateInformation("Index to loc format", font.indexToLocFormat, details);
            CreateInformation("Glyph data format", font.glyphDataFormat, details);

            var width = font.xMax - font.xMin;
            var height = font.yMax - font.yMin;
            var scale = 64 / font.unitsPerEm;

            var container = document.getElementById("font-container");

            while (container.firstChild) {
                container.removeChild(container.firstChild);
            }

            for (var i = 0; i < font.length; i++) {
                var canvas = document.createElement("canvas");
                canvas.style.border = "1px solid gray";
                canvas.width = width * scale;
                canvas.height = height * scale;
                var ctx = canvas.getContext("2d");
                ctx.scale(scale, -scale);
                ctx.translate(-font.xMin, -font.yMin - height);
                ctx.fillStyle = "#000000";
                ctx.beginPath();
                if (font.drawGlyph(i, ctx)) {
                    ctx.fill();
                    container.appendChild(canvas);
                }
            }
        }

    </script>
</body>

</html>